#include <slothttpd/parser/request_line_parser.hpp>

#include <cstring>
#include <cassert>

#include <string>

namespace slothttpd {
namespace parser {

request_line_parser::request_line_parser() noexcept
{
    std::memset(&d, 0, sizeof d);
}

result_type request_line_parser::parse(receive_buffer_list &list)
{
    char_range_t b = list.get_data();
    std::ptrdiff_t consumed = 0;
    
    while (!b.empty() && (b.front() == '\n' || b.front() == '\r')) {
        b.advance_begin(1);
        consumed++;
    }
    
    if (b.empty())
        return parse_again;
    
    ngx_buf_t old, buf;

    old.pos  = reinterpret_cast<unsigned char*>(b.begin());
    old.last = reinterpret_cast<unsigned char*>(b.end());

    buf = old;
    ngx_int_t r = ngx_http_parse_request_line(&d, &buf);
    
    consumed += buf.pos - old.pos;
    list.consume(consumed);

    switch (r) {
    case NGX_OK:
        for (char &b : path()) {
            if (b == '#') {
                d.uri_end = reinterpret_cast<unsigned char*>(&b);
                break;
            }
        }
        
        return parse_ok;

    case NGX_AGAIN:
        return parse(list);

    case NGX_HTTP_PARSE_INVALID_METHOD:
        return parse_error;

    case NGX_HTTP_PARSE_INVALID_REQUEST:
        return parse_error;

    case NGX_HTTP_PARSE_INVALID_09_METHOD:
        return parse_error;

    default:
        return parse_error;
    }
}

http::version_t request_line_parser::http_version() const noexcept
{
    int v = d.http_version;
    return std::make_pair(v / 1000, v % 1000);
}

char_range_t request_line_parser::method_name() const noexcept
{
    return char_range_t(reinterpret_cast<char*>(d.request_start),
                        reinterpret_cast<char*>(d.method_end + 1));
}

http::method::method_type request_line_parser::method() const noexcept
{
    using namespace http;
    
    switch(d.method) {
    case NGX_HTTP_UNKNOWN:
        return method::unknown;
        
    case NGX_HTTP_GET:
        return method::get;
        
    case NGX_HTTP_HEAD:
        return method::head;
        
    case NGX_HTTP_POST:
        return method::post;
        
    case NGX_HTTP_PUT:
        return method::put;
        
    case NGX_HTTP_DELETE:
        return method::delete_;
        
    case NGX_HTTP_MKCOL:
        return method::mkcol;
        
    case NGX_HTTP_COPY:
        return method::copy;
        
    case NGX_HTTP_MOVE:
        return method::move;
        
    case NGX_HTTP_OPTIONS:
        return method::options;
        
    case NGX_HTTP_PROPFIND:
        return method::propfind;
        
    case NGX_HTTP_PROPPATCH:
        return method::proppatch;
        
    case NGX_HTTP_LOCK:
        return method::lock;
        
    case NGX_HTTP_UNLOCK:
        return method::unlock;
        
    case NGX_HTTP_PATCH:
        return method::patch;
        
    case NGX_HTTP_TRACE:
        return method::trace;
    
    default:
        return method::unknown;
    }
}

char_range_t
request_line_parser::schema() const noexcept
{
    return char_range_t(reinterpret_cast<char*>(d.schema_start),
                        reinterpret_cast<char*>(d.schema_end));
}

char_range_t
request_line_parser::host() const noexcept
{
    return char_range_t(reinterpret_cast<char*>(d.host_start),
                        reinterpret_cast<char*>(d.host_end));
}

char_range_t
request_line_parser::port() const noexcept
{
    if (d.port_end != nullptr) {
        return char_range_t(reinterpret_cast<char*>(d.host_end + 1),
                            reinterpret_cast<char*>(d.port_end));
    } else {
        return char_range_t(nullptr, nullptr);
    }
}

char_range_t request_line_parser::path() const noexcept
{
    unsigned char *path_end;
    if (d.args_start != nullptr) {
        path_end = d.args_start - 1;
    } else {
        path_end = d.uri_end;
    }
    return char_range_t(reinterpret_cast<char*>(d.uri_start),
                        reinterpret_cast<char*>(path_end));
}

char_range_t request_line_parser::path_extension() const noexcept
{
    if (d.uri_ext != nullptr) {
        return char_range_t(reinterpret_cast<char*>(d.uri_ext), 
                            reinterpret_cast<char*>(path().end()));
    } else {
        return char_range_t(nullptr, nullptr);
    }
}

char_range_t request_line_parser::query_string() const noexcept
{
    if (d.args_start == nullptr) {
        return char_range_t(nullptr, nullptr);
    }
    
    return char_range_t(reinterpret_cast<char*>(d.args_start),
                        reinterpret_cast<char*>(d.uri_end));
}

bool request_line_parser::is_quoted_uri() const noexcept
{
    return d.complex_uri || d.quoted_uri || d.plus_in_uri;
}

}
}

